/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.wsinfra.wsrm.impl;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Vector;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.XmlCursor;

import cgl.narada.wsinfra.WsMessageFlow;
import cgl.narada.wsinfra.WsProcessor;
import cgl.narada.wsinfra.exception.DeploymentException;
import cgl.narada.wsinfra.exception.IncorrectExchangeException;
import cgl.narada.wsinfra.exception.MessageFlowException;
import cgl.narada.wsinfra.exception.ParsingException;
import cgl.narada.wsinfra.exception.ProcessingException;
import cgl.narada.wsinfra.exception.UnknownExchangeException;
import cgl.narada.wsinfra.exception.WsFaultException;
import cgl.narada.wsinfra.schemas.addressing.ActionDocument;
import cgl.narada.wsinfra.schemas.addressing.EndpointReferenceType;
import cgl.narada.wsinfra.schemas.addressing.FromDocument;
import cgl.narada.wsinfra.schemas.addressing.RelatesToDocument;
import cgl.narada.wsinfra.schemas.addressing.Relationship;
import cgl.narada.wsinfra.schemas.addressing.ToDocument;
import cgl.narada.wsinfra.schemas.soap.EnvelopeDocument;
import cgl.narada.wsinfra.schemas.soap.EnvelopeType;
import cgl.narada.wsinfra.schemas.utility.AttributedURI;
import cgl.narada.wsinfra.schemas.wsrm.AckRequestedDocument;
import cgl.narada.wsinfra.schemas.wsrm.SequenceAcknowledgementDocument;
import cgl.narada.wsinfra.schemas.wsrm.SequenceFaultDocument;
import cgl.narada.wsinfra.schemas.wsrm.SequenceRefDocument;
import cgl.narada.wsinfra.schemas.wsrm.SequenceRefType;
import cgl.narada.wsinfra.schemas.wsrm.TerminateSequenceDocument;
import cgl.narada.wsinfra.util.QNameLocator;
import cgl.narada.wsinfra.wsa.AddressingHeaders;
import cgl.narada.wsinfra.wsa.ParseWsaHeaders;
import cgl.narada.wsinfra.wsa.WsaEnvelopeCreator;
import cgl.narada.wsinfra.wsa.WsaEprRules;
import cgl.narada.wsinfra.wsa.WsaFaultRules;
import cgl.narada.wsinfra.wsa.WsaProcessingFactory;
import cgl.narada.wsinfra.wsrm.WsrmAckOperations;
import cgl.narada.wsinfra.wsrm.WsrmActions;
import cgl.narada.wsinfra.wsrm.WsrmElementAddition;
import cgl.narada.wsinfra.wsrm.WsrmElementCreation;
import cgl.narada.wsinfra.wsrm.WsrmExchangeInfo;
import cgl.narada.wsinfra.wsrm.WsrmExchangeInfoCreator;
import cgl.narada.wsinfra.wsrm.WsrmFaults;
import cgl.narada.wsinfra.wsrm.WsrmNodeUtils;
import cgl.narada.wsinfra.wsrm.WsrmProcessingFactory;
import cgl.narada.wsinfra.wsrm.WsrmQNames;
import cgl.narada.wsinfra.wsrm.WsrmRequestCreator;
import cgl.narada.wsinfra.wsrm.WsrmSequencePolicies;
import cgl.narada.wsinfra.wsrm.WsrmSequencePolicyFactory;
import cgl.narada.wsinfra.wsrm.storage.WsrmAuditStorageOperations;
import cgl.narada.wsinfra.wsrm.storage.WsrmPolicyStorageOperations;
import cgl.narada.wsinfra.wsrm.storage.WsrmProtocolStorageOperations;
import cgl.narada.wsinfra.wsrm.storage.WsrmSequenceInfo;
import cgl.narada.wsinfra.wsrm.storage.WsrmSequenceInfoStorageOperations;
import cgl.narada.wsinfra.wsrm.storage.WsrmStorageEventsFactory;
import cgl.narada.wsinfra.wsrm.storage.WsrmStorageException;
import cgl.narada.wsinfra.wsrm.storage.WsrmStorageService;
import cgl.narada.wsinfra.wsrm.storage.WsrmStorageWidget;

/** This is a utility class which performs several functions that are
    helpful for the processing logic related to the WsrmSourceNode class.

    @author Shrideep Pallickara
    $Date: 2005/07/29 22:41:30 $
    $Revision: 1.7 $
*/

public class WsrmSourceNodeHelper extends WsProcessor {
  
  private static final WsrmSourceNodeHelper instance = new WsrmSourceNodeHelper();
  private EndpointReferenceType sourceEpr;
  private WsrmQNames wsrmQNames;
  private WsrmFaults wsrmFaults;
  private WsrmSequencePolicyFactory wsrmSequencePolicyFactory;  
  private WsrmExchangeInfoCreator wsrmExchangeInfoCreator;
  private WsrmAckOperations wsrmAckOperations;
  private WsrmElementAddition wsrmElementAddition;
  private WsrmElementCreation wsrmElementCreation;
  private WsrmRequestCreator wsrmRequestCreator;
  private WsrmNodeUtils wsrmNodeUtils;
  private WsrmActions wsrmActions;

  private WsrmStorageService wsrmStorageService;
  private WsrmProtocolStorageOperations wsrmProtocolOps;
  private WsrmSequenceInfoStorageOperations wsrmSequenceInfoOps;
  private WsrmPolicyStorageOperations wsrmPolicyOps;
  private WsrmAuditStorageOperations wsrmAuditOps;
  private WsrmStorageEventsFactory storageEventsFactory;

  private WsaFaultRules wsaFaultRules;
  private WsaEprRules wsaEprRules;
  private WsaEnvelopeCreator wsaEnvelopeCreator;
  private ParseWsaHeaders parseWsaHeaders; 
  private QNameLocator qNameLocator;

  private WsMessageFlow wsMessageFlow;
  private String moduleName = "WsrmSourceNodeHelper: ";
  
  private WsrmSourceNodeHelper() {
    wsrmQNames =  WsrmQNames.getInstance();
    wsrmFaults = WsrmProcessingFactory.getWsrmFaults();
    wsrmSequencePolicyFactory = 
      WsrmProcessingFactory.getWsrmSequencePolicyFactory();  
    wsrmExchangeInfoCreator = 
      WsrmProcessingFactory.getWsrmExchangeInfoCreator();
    wsrmAckOperations = WsrmProcessingFactory.getWsrmAckOperations();
    wsrmElementAddition = WsrmProcessingFactory.getWsrmElementAddition();
    wsrmElementCreation = WsrmProcessingFactory.getWsrmElementCreation();
    wsrmRequestCreator = WsrmProcessingFactory.getWsrmRequestCreator();
    wsrmNodeUtils = WsrmProcessingFactory.getWsrmNodeUtils();
    wsrmActions = WsrmActions.getInstance();
    storageEventsFactory = WsrmStorageEventsFactory.getInstance();

    wsaFaultRules =  WsaProcessingFactory.getWsaFaultRules();
    wsaEprRules = WsaProcessingFactory.getWsaEprRules();
    wsaEnvelopeCreator = WsaProcessingFactory.getWsaEnvelopeCreator();
    parseWsaHeaders = WsaProcessingFactory.getParseWsaHeaders();

    qNameLocator = QNameLocator.getInstance();
  }


  public void initialize(WsrmStorageService wsrmStorageService) 
    throws DeploymentException {
    this.wsrmStorageService = wsrmStorageService;
    if (wsrmStorageService == null) {
      throw new DeploymentException(moduleName + "StorageService is NULL!");
    }
    
    try {
      wsrmProtocolOps = wsrmStorageService.getWsrmProtocolStorageOperations();
      wsrmSequenceInfoOps = 
	wsrmStorageService.getWsrmSequenceInfoStorageOperations(); 
      wsrmPolicyOps = wsrmStorageService.getWsrmPolicyStorageOperations();
      wsrmAuditOps =  wsrmStorageService.getWsrmAuditStorageOperations();
      
    } catch (WsrmStorageException wsrmStorageException) {
      String reason = moduleName + "Problems initializing storage services\n"
	+ wsrmStorageException.toString();
      throw new DeploymentException(reason);
    }
  }

  
  public void setEndpointReference(EndpointReferenceType endpointReference) {
    this.sourceEpr = endpointReference;
  }

  public boolean 
  processExchange(EnvelopeDocument envelopeDocument,  int direction)
    throws UnknownExchangeException, IncorrectExchangeException,
    ProcessingException, MessageFlowException {

    String errorReport = null;
    
    errorReport = moduleName + "Should NOT have this exchange. This is "
      + "just a helper to the WsrmSourceNode. ";
    
    if (errorReport != null) {
      throw new ProcessingException(errorReport);
    }
    return false;
  }

  /** Sets the message flow which the processor should use */
  public void setMessageFlow(WsMessageFlow wsMessageFlow) 
    throws DeploymentException {
    this.wsMessageFlow = wsMessageFlow;
  }


  /** Gets the message flow which the processor should use. */
  public WsMessageFlow getMessageFlow() {
    return wsMessageFlow;
  }

  public static WsrmSourceNodeHelper getInstance() {
    return instance;
  }

  /** This method locates/retrieves/creates the WsrmSequencePolicies associated
     with the sequenceIdentifier. There are Three steps involved here.
     <br>Check to see if there is a SequenceRef document. IFF there is one, 
     check to see if the match is wsrm:Exact or wsrm:Prefix. If it is 
     wsrm:Prefix proceed to store the created WsrmSequencePolicies in the 
     policyStorage operations. If a previous entry exists for the SAME PREFIX 
     this should replace it.
     
     <br><br>If there is no SequenceRef document, check to see if a prefix 
     match exists, if there is one proceed to use the previously stored 
     sequence policies.

     <br><br>If there is NO sequenceRef document and NO matching PREFIX entries
     proceed to simply use the defaultWsrmSequencePolicies.*/
  public WsrmSequencePolicies  
  getWsrmSequencePolicies(EnvelopeDocument envelopeDocument,
			  String sequenceIdentifier) 
    throws WsrmStorageException { 
    WsrmSequencePolicies wsrmSequencePolicies = null;
    /*Process sequence ref */
    SequenceRefDocument sequenceRefDocument =
      wsrmSequencePolicyFactory.getSequenceRefDocument(envelopeDocument);
    
    if (sequenceRefDocument != null) {
      wsrmSequencePolicies = 
	wsrmSequencePolicyFactory.createWsrmSequencePolicies(envelopeDocument);
      SequenceRefType sequenceRef = sequenceRefDocument.getSequenceRef();
      if (sequenceRef.isSetMatch()) {
	QName matchType = sequenceRef.getMatch();	
	if (matchType.equals(wsrmQNames.getPrefix()) ) {
	  String prefixIdentifier = sequenceRef.getIdentifier();
	  if (prefixIdentifier != null) {
	    wsrmPolicyOps.storePolicyInformation(prefixIdentifier, 
						 wsrmSequencePolicies);
	  }
	} /** if equals PREFIX */
      } /** if isSetMATCH */
    } /* if sequenceRef Document */
    

    /** If there is no SequenceRef document, check to see if a prefix match 
	exists, if there is one proceed to use the previously stored 
	sequence policies. */
    
    String[] prefixes = wsrmPolicyOps.getSequencePrefixes();
    if (prefixes != null) {
      for (int i=0; i < prefixes.length ; i++) { 
	if (sequenceIdentifier.startsWith(prefixes[i]) ) {
	  wsrmSequencePolicies = 
	    wsrmPolicyOps.getWsrmSequencePolicies(prefixes[i]);
	  return wsrmSequencePolicies;
	}
      }
    }

    /** If there is NO sequenceRef document and NO matching PREFIX entries
	proceed to simply use the defaultWsrmSequence Policies */
    wsrmSequencePolicies =
      wsrmSequencePolicyFactory.getDefaultWsrmSequencePolicies();
    
    return wsrmSequencePolicies;
  }



  
  
  /** Creates a WsrmSequenceInfo based on the specified parameters. If the
      [wsa:From] is NULL or [wsa:ReplyTo] is NULL proceed to create a new EPR
      for the sink using the specified destination. */
  public WsrmSequenceInfo 
  getWsrmSequenceInfo(AddressingHeaders addressingHeaders,
		      String sequenceIdentifier, String relatesTo, 
		      String destination, 
		      WsrmSequencePolicies wsrmSequencePolicies) 
    throws WsrmStorageException {
    EndpointReferenceType sinkEpr = wsaEprRules.sendTo(addressingHeaders);
    
    if (sinkEpr == null) {
      sinkEpr = EndpointReferenceType.Factory.newInstance();
      sinkEpr.addNewAddress().setStringValue(destination);
    }
    
    boolean isSource = true;
    WsrmSequenceInfo wsrmSequenceInfo =
      storageEventsFactory.createWsrmSequenceInfo(sequenceIdentifier,relatesTo,
						  sourceEpr, sinkEpr, 
						  isSource);

    storageEventsFactory.attachPolicyToSequence(wsrmSequenceInfo, 
						wsrmSequencePolicies);
    return wsrmSequenceInfo;
  }

  



  /** This method processes a set of acknowledgements received for a sequence.
      It checks, and issues faults if problems are encountered, for three
      distinct conditions.
      <br>(a) Is this a known sequence?
      <br>(b) If the sequence acknowledgement message numbers a valid one?
      <br>(c) Has this sequence expired/terminated? */
  public void 
  checkForProblems(AddressingHeaders addressingHeaders,
		   SequenceAcknowledgementDocument sequenceAckDocument,
		   long[] acknowledgements) 
    throws WsrmStorageException, WsFaultException { 
    String errorReport = "";
    SequenceAcknowledgementDocument.SequenceAcknowledgement acknowledgement =
      sequenceAckDocument.getSequenceAcknowledgement();
    AttributedURI attributedUri = acknowledgement.getIdentifier();
    if (attributedUri == null) {
      errorReport = moduleName + "There is NO wsu:Identifier element in the " +
	"[wsrm:Sequence] element. Check the created message.";
      throwInvalidMessageFaultException(errorReport, addressingHeaders);
    }

    String sequenceIdentifier = attributedUri.getStringValue();
    
    if (!wsrmSequenceInfoOps.isKnownSequence(sequenceIdentifier) ) {
      throwUnknownSequenceFaultException(addressingHeaders, 
					 sequenceIdentifier);
    }
    
    
    boolean sequenceProblems = false;
    WsrmSequenceInfo wsrmSequenceInfo =
      wsrmSequenceInfoOps.getSequenceInfoUsingSequenceIdentifier(sequenceIdentifier);
    
    if (wsrmSequenceInfo.isTerminated()) {
      errorReport = "The specified sequence [" + sequenceIdentifier + 
	"] has been [Terminated] because " + 
	wsrmSequenceInfo.getTerminationReason(); 
      throwInvalidAcknowledgementFaultException(addressingHeaders, 
						sequenceAckDocument, 
						errorReport);
    }
    
    if (acknowledgements != null) {
      Arrays.sort(acknowledgements);
      long maxAckMessageNumber = acknowledgements[acknowledgements.length -1 ];
      checkMessageNumberProblem(addressingHeaders, sequenceAckDocument,
				sequenceIdentifier, maxAckMessageNumber);
    }
  }





  /** Retrieve the negative acknowledgements that have been specified within
      the sequence acknowledgement as an array of longs. The conversions of
      the datatypes needs to be handled by the method. */
  public long[] 
  getNegativeAcknowledgements(SequenceAcknowledgementDocument 
			      sequenceAckDocument) {
    long[] nacks = null;
    SequenceAcknowledgementDocument.SequenceAcknowledgement acknowledgement =
      sequenceAckDocument.getSequenceAcknowledgement();
    int nackSize = acknowledgement.sizeOfNackArray();
    
    if (nackSize == 0) {
      return nacks;
    }
    nacks = new long[nackSize];
    BigInteger[] nackArray = acknowledgement.getNackArray();

    for (int i=0; i < nackSize; i++) {
      nacks[i] = nackArray[i].longValue();
    }
    return nacks;
  }



  /** Check to see if the specified maxAckNumber contained in acknowledgement
      is a valid one. If it is not proceed to construct a WsFaultException
      that contains the SequenceAcknowledgementDocument and throw that 
      exception */
  private void 
  checkMessageNumberProblem(AddressingHeaders addressingHeaders, 
			    SequenceAcknowledgementDocument seqAckDocument,
			    String sequenceIdentifier,
			    long maxAckMessageNumber) 
    throws WsrmStorageException, WsFaultException {
    long currentMaxMessageNumber = 
      wsrmProtocolOps.getMessageNumberLastAssigned(sequenceIdentifier);
    if (maxAckMessageNumber > currentMaxMessageNumber) {
      String error =("SequenceAcknowledgement contains invalid message number="
		     + maxAckMessageNumber);
      throwInvalidAcknowledgementFaultException(addressingHeaders,
						seqAckDocument, error);
    }
  }

  
  /** This method manages retransmissions based on the supplied negative
      acknowledgements and the sequence identifier. The sequence identifier
      is used to determine the message numbers from which the retransmission
      interval has expired and no acknowledgement has been received. */
  public void manageRetransmissions(String sequenceIdentifier, 
				    long[] nacks) 
  throws WsrmStorageException, MessageFlowException {
    
    /** Check acks, and determine missing numbers for which the ackInterval
	has expired.*/
    long[] missedAcks = 
      wsrmProtocolOps.getUnacknowledgedMessageNumbers(sequenceIdentifier,
						      false);
    long[] retransmissions = getCombinedArrayOfLong(missedAcks, nacks);

    if (retransmissions == null) {
      return;
    }
    

    /** Create the right AckRequested element. If there is a lastMessage
	number available, then the maxMessageNumberUsed element should be
	presented within the AckRequested element. */
    WsrmSequenceInfo wsrmSequenceInfo =
      wsrmSequenceInfoOps.getSequenceInfoUsingSequenceIdentifier(sequenceIdentifier);
    boolean hasLastMessageInfo = wsrmSequenceInfo.hasLastMessageInfo();
    long lastMessageNumber = wsrmSequenceInfo.getLastMessageNumber();
    
    AckRequestedDocument ackRequested = null;
    if (hasLastMessageInfo) {
      ackRequested = wsrmElementCreation.newAckRequested(sequenceIdentifier,
							 lastMessageNumber);
    } else {
      ackRequested = wsrmElementCreation.newAckRequested(sequenceIdentifier);
    }

    

    int numOfRetransmissions = retransmissions.length;
    for (int i=0; i < numOfRetransmissions; i++) { 
      WsrmStorageWidget wsrmStorageWidget =
	wsrmProtocolOps.getStoredElement(sequenceIdentifier, 
					 retransmissions[i]);
      EnvelopeDocument envelopeDocument = wsrmStorageWidget.getEnvelope();
      boolean success = wsrmElementAddition.addAckRequested(envelopeDocument,
							    ackRequested);
      if (!success) {
	System.out.println(moduleName + "Problems adding ackRequested to " +
			   "envelope in messageNumber=" + retransmissions[i] + 
			   ", with sequence = " + sequenceIdentifier);
	continue;
      }
      
      /** Send the enevelope over the network. */
      enrouteToNetwork(envelopeDocument);
    }
  }





  /** Check to see if the sequence needs to be terminated, and if so, proceed
      to create and issue the appropriate termination sequence exchange */
  public void processSequenceTermination(String sequenceIdentifier) 
    throws WsrmStorageException, MessageFlowException {
    WsrmSequenceInfo wsrmSequenceInfo = 
      wsrmSequenceInfoOps.getSequenceInfoUsingSequenceIdentifier(sequenceIdentifier);
    
    if (!wsrmSequenceInfo.hasLastMessageInfo()) {
      /** The sequence cannot be terminated since the last message number has
	  not yet been issued */
      return;
    }
    System.out.println(moduleName + "Sequence [" + sequenceIdentifier + 
		       "] has a LastMessageNumber=" + 
		       wsrmSequenceInfo.getLastMessageNumber() );
    
    long[] stillToBeAcked = 
      wsrmProtocolOps.getUnacknowledgedMessageNumbers(sequenceIdentifier, 
						      true);
    
    if (stillToBeAcked != null) {
      String diagnosticInfo = moduleName + "(" + stillToBeAcked.length +
	") MessageNumbers needs to be acknowledged.\n";
      for (int i=0; i < stillToBeAcked.length; i++) {
	diagnosticInfo += ( "[" + stillToBeAcked[i] + "] " );
      }
      System.out.println(diagnosticInfo);
      /** There are still message numbers that need to acknowledged. Return.*/
      return;
    }
    
    /** Proceed to terminate sequence */
    String reason = "All messages have acknowledged, and the sequence is " +
      "ready to be terminated. ";
    storageEventsFactory.terminateSequence(wsrmSequenceInfo, reason);
    wsrmSequenceInfoOps.storeCreatedSequence(wsrmSequenceInfo);
    
    TerminateSequenceDocument terminateSequenceDocument =
      wsrmElementCreation.newTerminateSequence(sequenceIdentifier);
    
    EndpointReferenceType to = wsrmSequenceInfo.getDestination();
    
    FromDocument from = FromDocument.Factory.newInstance();
    from.setFrom(sourceEpr);
    
    ActionDocument action = ActionDocument.Factory.newInstance();
    action.addNewAction().setStringValue(wsrmActions.getTerminateSequence());

    String relatesToId = wsrmSequenceInfo.getAddressingIdentifier();
    RelatesToDocument relatesTo = RelatesToDocument.Factory.newInstance();
    relatesTo.addNewRelatesTo().setStringValue(relatesToId);

    EnvelopeDocument envelopeDocument = 
      wsaEnvelopeCreator.createSoapEnvelope(to, from, action, relatesTo);
    wsrmElementAddition.addTerminateSequence(envelopeDocument,
					     terminateSequenceDocument);
    
    /** Send the enevelope over the network. */
    enrouteToNetwork(envelopeDocument);
    
    /** Proceed to remove the sequenceId info for the destination. This will
	ensure that the next time a message is sent to a destination, a new
	sequence identifier will be created. */
    String destination = wsrmSequenceInfoOps.getDestinationForSequenceIdentifier(sequenceIdentifier);
    wsrmSequenceInfoOps.removeDestinationSequenceIdentifierPair(destination);

  }



  /** Check to see if an element which indicates that it is last message
      is present. If so return true, else return false. */
  public boolean isLastMessageOfSequence(EnvelopeDocument envelopeDocument) {
    String value = getValueAssociatedWithQName(envelopeDocument,
					       wsrmQNames.getLastMessageNBX());
    if (value == null) {
      return false;
    }

    if (value.equals("true")) {
      return true;
    }    
    return false;
  }


  /** Check to see if an acknowledgement has been requested for this 
      message. Check to see if the element exists, if it does return true;
      else return false.*/
  public boolean isAckRequested(EnvelopeDocument envelopeDocument) {
    String value =getValueAssociatedWithQName(envelopeDocument,
					      wsrmQNames.getAckRequestedNBX());
    if (value == null) {
      return false;
    }
    
    if (value.equals("true")) {
      return true;
    }    
    return false;
  }


  /** Check to see if an acknowledgement has been requested for this 
      message. Check to see if the element exists, if it does return true;
      else return false.*/
  public Calendar getSequenceExpiresAt(EnvelopeDocument envelopeDocument) {
    Calendar expiresAt = null;
    
    String value = 
      getValueAssociatedWithQName(envelopeDocument, 
				  wsrmQNames.getSequenceExpiresAtNBX() );
    if (value == null) {
      return expiresAt;
    }
    
    try {
      long expiryValue = Long.parseLong(value);
      expiresAt = Calendar.getInstance();
      expiresAt.setTimeInMillis(expiryValue); 
    } catch (Exception e) {
      System.out.println(moduleName + "Specified expiration time [" +
			 value + "] is NOT a valid long (expected) value.");
    }
    return expiresAt;
  }


  /** Retrieves the value associated with a QName name within an Envelope.
      Note that this method is used ONLY for the proprietary QNames that
      are added by an application. These QNames are removed by this
      method once it retrieves the value. */
  private String 
  getValueAssociatedWithQName(EnvelopeDocument envelopeDocument,
			      QName qName) {
    if (envelopeDocument == null || qName == null) {
      return null;
    }

    EnvelopeType envelopeType = envelopeDocument.getEnvelope();
    if (envelopeType == null) {
      return null;
    }
    
    if (!envelopeType.isSetHeader()) {
      return null;
    }
    
    XmlCursor headerCursor = envelopeType.getHeader().newCursor();
    boolean found = qNameLocator.locateQName(headerCursor, qName);
    
    if (!found) {
      return null;
    }
    
    String value = headerCursor.getTextValue();
    /** Now that we have retrieved the value, let us remove the QName. */
    headerCursor.removeXml();
    headerCursor.dispose();

    return value;
  }


  /** This method enforces the rule that the [wsa:To] element should be
      present within a SOAP header element. If this is not present an
      exception needs to be thrown. */
  public void 
  checkForProblemsInToDocument(AddressingHeaders addressingHeaders)
    throws WsFaultException {
    ToDocument toDocument = addressingHeaders.getTo();
    String errorReport = "Problems with the [wsa:To] element. ";
    boolean problems = false;
    if (toDocument == null) {
      errorReport += ("This CANNOT be null. It has to be specified!");
      problems = true;
    }
    
    if (!problems) {
      String to = toDocument.getTo().getStringValue();
      errorReport += ("The value of the <To> element cannot be NULL.");
    } 
    
    if (problems) {
      throwInvalidMessageFaultException(errorReport, addressingHeaders);
    }
  }


  /** This method parses the SOAP envelope and retrieves the [wsa:RelatesTo]
      element within the SOAP header element as a String. This method
      returns a NULL if this element is NOT found.*/
  public String getRelatesTo(EnvelopeDocument envelopeDocument) {
    String relatesTo = null;
    try {
      RelatesToDocument relatesToDocument = 
	parseWsaHeaders.getRelatesTo(envelopeDocument);

      if (relatesToDocument == null) {
	return relatesTo;
      }
      Relationship relationship = relatesToDocument.getRelatesTo();
      if (relationship == null) {
	return relatesTo;
      }
      
      relatesTo = relationship.getStringValue();

    } catch (ParsingException parseEx) {
      /** Simply implies that there are problems parsing. Program logic will
	  cope with this as a valid relatesTo not being specified. */
      System.out.println(moduleName + "Problems parsing [wsa:relatesTo] " +
			 "element within the Envelope");
    }

    return relatesTo;
  }



  /** Checks to see if a message number rollver is about to occur. That is 
      the message number has a value of Long.MAX_VALUE. If this happens,
      the sequence needs to be terminated (the corresponding WSRM sequence
      info being stored) and a WsFault exception needs to be issued. */
  public void checkForMessageRolloverFault(WsrmSequenceInfo wsrmSequenceInfo,
					    long previousMessageNumber) 
    throws WsFaultException, WsrmStorageException {
    
    if (previousMessageNumber == Long.MAX_VALUE) {
      String terminateReason = wsrmFaults.getMessageNumberRolloverReason();
      storageEventsFactory.terminateSequence(wsrmSequenceInfo, 
					     terminateReason);
      wsrmSequenceInfoOps.storeCreatedSequence(wsrmSequenceInfo);

      String sequenceIdentifier = wsrmSequenceInfo.getSequenceIdentifier();
      EndpointReferenceType faultTo = wsrmSequenceInfo.getDestination();
      throwMessageRolloverFaultException(sequenceIdentifier, faultTo);      
    }
  }
  


  /** Throws a WsFaultException corresponding to a message number rollover
      exception. */
  private void 
  throwMessageRolloverFaultException(String identifier,
				     EndpointReferenceType faultTo) 
    throws WsFaultException {
    WsFaultException wsFaultException = 
      new WsFaultException(wsrmFaults.getSoapSenderQName(),
			   wsrmFaults.getMessageNumberRolloverReason() );
    SequenceFaultDocument sequenceFaultDocument = 
      wsrmFaults.getMessageNumberRollover(identifier);
    
    wsFaultException.setAddSoapHeaderElement( sequenceFaultDocument );
    wsFaultException.setFaultTo(faultTo);
    
    throw wsFaultException;
  }
  

  /** Throws an Invalid MessageFault exception based on the specified
      parameters */
  public void 
  throwInvalidMessageFaultException(String reason, 
				    AddressingHeaders addressingHeaders) 
    throws WsFaultException {
    EndpointReferenceType faultTo = 
      wsaFaultRules.sendFaultTo(addressingHeaders);
    
    WsFaultException wsFaultException = 
      new WsFaultException(wsrmFaults.getSoapSenderQName(),
			   reason);
    SequenceFaultDocument sequenceFaultDocument = 
      wsrmFaults.getInvalidMessage();

    wsFaultException.setAddSoapHeaderElement( sequenceFaultDocument );
    wsFaultException.setFaultTo(faultTo);
    
    throw wsFaultException;
  }


  /** Throws an Invalid SequenceAcknowledgement Fault exception based on the 
      specified  parameters */
  public void 
  throwInvalidAcknowledgementFaultException(AddressingHeaders addressingHeaders, SequenceAcknowledgementDocument seqAckDocument, String additionalReason) 
    throws WsFaultException {
    EndpointReferenceType faultTo = 
      wsaFaultRules.sendFaultTo(addressingHeaders);
    
    String reason = wsrmFaults.getInvalidAcknowledgementReason() +  " " +
      additionalReason;
    

    WsFaultException wsFaultException = 
      new WsFaultException(wsrmFaults.getSoapSenderQName(), reason);
    SequenceFaultDocument sequenceFaultDocument = 
      wsrmFaults.getInvalidAcknowledgement(seqAckDocument);

    wsFaultException.setAddSoapHeaderElement( sequenceFaultDocument );
    wsFaultException.setFaultTo(faultTo);
    
    throw wsFaultException;
  }


  /** Throws an UnknownSequence Fault exception based on the specified
      parameters */
  public void 
  throwUnknownSequenceFaultException(AddressingHeaders addressingHeaders,
				     String identifier) 
    throws WsFaultException {
    EndpointReferenceType faultTo = 
      wsaFaultRules.sendFaultTo(addressingHeaders);
    
    WsFaultException wsFaultException = 
      new WsFaultException(wsrmFaults.getSoapSenderQName(),
			   wsrmFaults.getUnknownSequenceReason() );
    SequenceFaultDocument sequenceFaultDocument = 
      wsrmFaults.getUnknownSequence(identifier);

    wsFaultException.setAddSoapHeaderElement( sequenceFaultDocument );
    wsFaultException.setFaultTo(faultTo);
    
    throw wsFaultException;
  }

  


  
  public void checkExchangeType(WsrmExchangeInfo wsrmExchangeInfo, 
				int direction)
    throws UnknownExchangeException, IncorrectExchangeException {

    if (direction == WsMessageFlow.FROM_NETWORK) {
      checkExchangeFromNetwork(wsrmExchangeInfo);
    }

    if (direction == WsMessageFlow.FROM_APPLICATION) {
      checkExchangeFromApplication(wsrmExchangeInfo);
    }
    
  }

  /** ONLY SequenceAcknowledgement(s) and CreateSequenceResponse(s) are
      valid exchanges that should be routed here. */
  private void 
  checkExchangeFromNetwork(WsrmExchangeInfo wsrmExchangeInfo)
    throws UnknownExchangeException, IncorrectExchangeException {
    
    String from = "Network. ";
    String problemElement = "";
    if (wsrmExchangeInfo.hasAckRequested() ) {
      problemElement += " AckRequested ";
    }
    
    if (wsrmExchangeInfo.hasSequence()) {
      problemElement += " Sequence ";
    }
    
    if (wsrmExchangeInfo.isCreateSequence() ) {
      problemElement += " CreateSequence ";
    }
    
    if (wsrmExchangeInfo.isTerminateSequence() ) {
      problemElement += " TerminateSequence ";
    }
    
    if (!problemElement.equals("")) {
      throwIncorrectExchangeException(problemElement, from);
    }
    
    
    if (wsrmExchangeInfo.isCreateSequenceResponse() || 
	wsrmExchangeInfo.hasSequenceAcknowledgement() ) {
      /** If this is true, the exchange is a valid one. Proceed to return */
      return;
    }
    
    String shouldHaveReceived = 
      "CreateSequenceResponse or SequenceAcknowledgement";
    throwUnknownExchangeException(shouldHaveReceived, from);
  }
  
  
  /** Only CreateSequence and WsrmMessages (messages without action elements
      or WSRM element) are valid. */
  private void 
  checkExchangeFromApplication(WsrmExchangeInfo wsrmExchangeInfo)
    throws UnknownExchangeException, IncorrectExchangeException {
    
    String from = "Application. ";
    String problemElement = "";
    if (wsrmExchangeInfo.hasAckRequested() ) {
      problemElement += " AckRequested ";
    }
    
    if (wsrmExchangeInfo.hasSequence()) {
      problemElement += " Sequence ";
    }
    
    if (wsrmExchangeInfo.hasSequenceAcknowledgement() ) {
      problemElement += " SequenceAcknowledgement ";
    }
    
    if (wsrmExchangeInfo.isCreateSequenceResponse() ) {
      problemElement += " CreateSequenceResponse ";
    }
    
    if (wsrmExchangeInfo.isTerminateSequence() ) {
      problemElement += " TerminateSequence ";
    }
    
    if (!problemElement.equals("")) {
      throwIncorrectExchangeException(problemElement, from);
    }

  }


  private void throwIncorrectExchangeException(String element, String from)
    throws IncorrectExchangeException {
    String reason =  moduleName +
      "Should NOT have received exchange with element [" +
      element + "] from the " + from;
    throw new IncorrectExchangeException(reason);
  }


  private void throwUnknownExchangeException(String element, String from)
    throws UnknownExchangeException {
    String reason =  moduleName +
      "No idea about processing exchange without elements [" + element + 
      "] from the " + from + ".";
    throw new UnknownExchangeException(reason);
  }


  /** Combines the two arrays to create a unique array without any
      duplicate values in the combined array. This will also eliminate any
      duplicates that might exist in the original arrays */
  private long[]
  getCombinedArrayOfLong(long[] first, long[] second) {
    if (first == null) {
      return second;
    }
    
    if (second == null) {
      return first;
    }
    
    Vector combinedVector = new Vector();
    for (int i=0; i < first.length; i++) {
      addToVector(combinedVector, first[i]);
    }
    
    for (int i=0; i < second.length; i++) {
      addToVector(combinedVector, second[i]);
    }
    
    if (combinedVector.size() == 0) {
      return null;
    }
    
    int numOfElements = combinedVector.size();
    long[] mergedArray = new long[numOfElements];
    for (int i=0; i < numOfElements; i++) {
      mergedArray[i] = ((Long)combinedVector.elementAt(i)).longValue();
    }
    
    return mergedArray;
  }
  
  /** Adds a message number to the vector */
  private static void addToVector(Vector vector, long value) {
    Long toAdd = new Long(value);
    if (vector.contains(toAdd)) {
      return;
    }
    vector.addElement(toAdd);
  }

}






